package org.batatasframework.support.definition.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.batatasframework.support.database.DatabaseType;
import org.batatasframework.support.database.JoinType;
import org.batatasframework.support.database.metadatas.AbstractTableMetadataProxy;
import org.batatasframework.support.database.metadatas.FieldMetadata;
import org.batatasframework.support.database.metadatas.TableMetadata;
import org.batatasframework.support.database.sql.TableRuntimeJoin;
import org.batatasframework.support.database.sql.TableInstance;
import org.batatasframework.support.database.sql.field.ArraySelectFragment;
import org.batatasframework.support.database.sql.field.DefaultSelectFragment;
import org.batatasframework.support.database.sql.field.FormatSelectFragment;
import org.batatasframework.support.database.sql.field.SelectFragment;
import org.batatasframework.support.database.sql.sort.SortFragment;
import org.batatasframework.support.database.sql.sort.SortType;
import org.batatasframework.support.database.sql.spi.TableCriterion;
import org.batatasframework.support.database.sql.where.QueryFragment;
import org.batatasframework.support.definition.ConditionDefinition;
import org.batatasframework.support.definition.FieldDefinition;
import org.batatasframework.support.definition.FieldDefinitionQueriable;
import org.batatasframework.support.definition.JoinDefinition;
import org.batatasframework.support.definition.QueryDefinition;
import org.batatasframework.support.definition.SortDefinition;
import org.batatasframework.support.definition.TableDefinition;
import org.batatasframework.support.definition.TableDefinitionResource;
import org.batatasframework.support.factory.DefaultListableTableFactory;
import org.batatasframework.support.searchable.FieldDisplayType;
import org.batatasframework.support.searchable.FieldDisplayable;
import org.batatasframework.support.searchable.FieldDisplayed;
import org.batatasframework.support.searchable.FieldQueryed;

import cn.bonoon.kernel.support.IOperator;

/**
 * <pre>
 * 定义一个表单，查询、汇总统计和编辑查看等操作都可以使用这个定义来规范数据；
 * 人机交互表单的定义
 * </pre>
 * @author jackson
 *
 */
public class TableDefinitionImpl extends AbstractTableMetadataProxy implements TableDefinition{

	private FieldDefinition[] columns;
	private final LinkedHashMap<String, FieldDefinition> columnMapped = new LinkedHashMap<>();
	
	private final ArrayList<FieldDisplayed> groupColumn   = new ArrayList<>();
	private final ArrayList<FieldDisplayed> displayColumn = new ArrayList<>();
	
	private DefaultListableTableFactory tableFactory;
//	/**
//	 * <pre>
//	 * 这个定义表主要是以哪一个表为主表来定义的，这个表并不一定就是执行时被查询的表; 
//	 * 但必须与最终被查询的表的表结构定义一致，即分表；否则抛出异常
//	 * </pre>
//	 */
//	private final TableMetadata sourceTableMetadata;

	/** 定义表时所使用到的所有关联表 */
	private final Map<TableMetadata, JoinDefinition> joinTables = new HashMap<>();
	private final List<JoinDefinition> mustJoinTables = new ArrayList<>();
	
	/** 
	 * <pre>界面查询条件提交后，对应的数据库查询语句生成的处理；
	 * 可以不定义字段而直接定义查询条件。{@link #queryColumn}是由查询定义生成的；
	 * 这里也改成有顺序的</pre>
	 */
	private final Map<String, QueryDefinition> columnQueriable = new LinkedHashMap<>();
	
	/** 添加默认的查询条件，即每次执行查询操作都需要添加的条件，如x.c_deleted=0等 */
	private final ArrayList<ConditionDefinition> conditionDefinitions = new ArrayList<>();
	private final ArrayList<SortDefinition> sortDefinitions = new ArrayList<>();
	//private final List<FieldDefinition> mustDisplayField=new ArrayList<>();
	public void put(String key, FieldDefinition column){
		columnMapped.put(key, column);
	}
	
	//@Override
	public void query(String key, QueryDefinition queryDefinition){
		columnQueriable.put(key, queryDefinition);
	}
	
	public void flush(){ 
		/*
		 * TODO 定义完成后的处理
		 * 
		 * 定义完成后，需要对数据进行处理以及初始化的工作，
		 * 生成各种需要的数据
		 */
		Collection<FieldDefinition> values = columnMapped.values();
		columns = values.toArray(new FieldDefinition[values.size()]);
		
		for(FieldDefinition fd : values){
			String key = fd.getKey();
			
			if(fd instanceof FieldDefinitionQueriable){
				//表示这个字段可用于查询
				FieldDefinitionQueriable fq = (FieldDefinitionQueriable)fd;
				if(fq.isQueryable()){
					query(key, fq.getQueryDefinition());
				}
			}
			
			if(fd instanceof FieldDisplayable){
				//也许。。有些字段是不能显示的或不是直接用于显示的
//				if(fd.isMustDiaplay()){
//					continue;
//				}
				FieldDisplayType displayType = ((FieldDisplayable) fd).getDisplayType();
				if(displayType == FieldDisplayType.NONE) continue;
				  Boolean unGroup = fd.unGroup();
				  if(null == unGroup || unGroup == false){
					  unGroup = false;
				  }
				
				FieldDisplayed fdDisplayed = new FieldDisplayed(key, fd.getTitle(), unGroup);
				if(displayType == FieldDisplayType.ITEM || displayType == FieldDisplayType.BOTH){
					if(!fd.isMustDiaplay()){
						displayColumn.add(fdDisplayed);
					}
				}
				
				if(displayType == FieldDisplayType.GROUP || displayType == FieldDisplayType.BOTH){
					groupColumn.add(fdDisplayed);
				}
			}
		}
		
		//以下的集合在定义完成之后就不会再改变的，所以可以使用优化
		groupColumn.trimToSize();
		displayColumn.trimToSize();
		sortDefinitions.trimToSize();
		conditionDefinitions.trimToSize();
	}
	
	public DefaultListableTableFactory getTableFactory() {
		return tableFactory;
	}
	
	@Override
	public QueryDefinition query(String key) {
		return columnQueriable.get(key);
	}
	
	@Override
	public List<ConditionDefinition> getConditions() {
		return conditionDefinitions;
	}
	
	@Override
	public List<SortDefinition> getSorts() {
		return sortDefinitions;
	}
	
	@Override
	public List<FieldDisplayed> getGroups() {
		return groupColumn;
	}

	@Override
	public List<FieldDisplayed> getDisplays() {
		return displayColumn;
	}

	@Override
	public List<FieldQueryed> getQuerys(IOperator operator) {
		List<FieldQueryed> queryColumn   = new ArrayList<>();
		for(Entry<String, QueryDefinition> entry : columnQueriable.entrySet()){
			QueryDefinition queryDefinition = entry.getValue();
			queryDefinition.buildFieldQueryed(queryColumn, entry.getKey(), operator, false);
		}
		return queryColumn;
	}
	
	public TableDefinitionImpl(DefaultListableTableFactory tableFactory, TableMetadata sourceTableMetadata) {
		super(sourceTableMetadata);
		this.tableFactory = tableFactory;
//		this.sourceTableMetadata = sourceTableMetadata;
	}
	
//	@Override
//	public TableMetadata getSourceTableMetadata() {
//		return sourceTableMetadata;
//	}
	
	public SortDefinition sort(TableDefinitionResource sourceTableDefinition, FieldMetadata sourceFieldMetadata){
		return sort(sourceTableDefinition, sourceFieldMetadata, null, 0);
	}
	
	public SortDefinition sort(TableDefinitionResource sourceTableDefinition, 
			FieldMetadata sourceFieldMetadata,
			SortType sortType, int ordinal){
		SortDefinition sortDefinition = new SimpleSortDefinition(sourceTableDefinition, 
				sourceFieldMetadata, sortType, ordinal);
		sortDefinitions.add(sortDefinition);
		return sortDefinition;
	}
	
	public class SimpleSortDefinition extends DefaultFieldDefinitionResource implements SortDefinition{
		public SimpleSortDefinition(TableDefinitionResource sourceTableDefinition, 
				FieldMetadata sourceFieldMetadata,
				SortType sortType, int ordinal) {
			super(sourceTableDefinition, sourceFieldMetadata);
			
			this.ordinal = ordinal;
			this.sortType = sortType;
		}

		private SortType sortType;
		private int ordinal;

		@Override
		public SortFragment buildSort(TableCriterion tableCriterion) throws Exception {
			TableInstance tableSource = tableCriterion.safeLoadSource(tableCriterion, sourceTableDefinition, false);
			if(null != tableSource){
				return tableCriterion.orderBy(tableSource, sourceFieldMetadata, sortType, ordinal);
			}
			return null;
		}
		
		@Override
		public void setOrdinal(int ordinal) {
			this.ordinal = ordinal;
		}
		
		@Override
		public void setSortType(SortType sortType) {
			this.sortType = sortType;
		}
	}
	
	public ConditionDefinition query(TableDefinitionResource tableDefinition, String query, boolean must){
		ConditionDefinition queryDefinition = new SimpleConditionDefinition(tableDefinition, query, must);
		conditionDefinitions.add(queryDefinition);
		return queryDefinition;
	}
	
	public class SimpleConditionDefinition implements ConditionDefinition{
		
		private final TableDefinitionResource tableDefinition;
		private final String query;
		private final boolean must;
		
		public SimpleConditionDefinition(TableDefinitionResource tableDefinition, String query, boolean must) {
			this.tableDefinition = tableDefinition;
			this.query = query;
			this.must = must;
		}

		@Override
		public QueryFragment buildQuery(TableCriterion tableCriterion) throws Exception {
			TableInstance tableSource = tableCriterion.safeLoadSource(tableCriterion, tableDefinition, must);
			if(null != tableSource){
				return tableCriterion.simpleQuery(tableSource, query);
			}
			return null;
		}
		
	}
	
	public JoinDefinition join(TableDefinitionResource tableDefinition, TableMetadata joinTableMetadata, boolean mustJoin){
		InternalJoinDefinition joinTable = new InternalJoinDefinition(tableDefinition, joinTableMetadata);
		joinTables.put(joinTableMetadata, joinTable);
		
		if(mustJoin){
			mustJoinTables.add(joinTable);
		}
		
		return joinTable;
	}
	
	private class InternalJoinDefinition extends AbstractTableMetadataProxy implements JoinDefinition{

		private String masterField;
		private String joinField;
		private JoinType type = JoinType.LEFT;
		private final TableDefinitionResource tableDefinition;
		
		public InternalJoinDefinition(TableDefinitionResource tableDefinition, TableMetadata joinTableMetadata){
			super(joinTableMetadata);
			this.tableDefinition  = tableDefinition; 
		}

		@Override
		public JoinDefinition on(JoinType type, String masterField, String joinField) {
			this.type = type;
			this.masterField = masterField;
			this.joinField = joinField;
			return this;
		}

		@Override
		public JoinDefinition on(String masterField) {
			return on(JoinType.LEFT, masterField, "C_ID");
		}
		
//		@Override
//		public TableMetadata getSourceTableMetadata() {
//			return this.sourceTableMetadata;
//		}
		
		@Override
		public TableRuntimeJoin loadJoinTable(TableCriterion tableCriterion, boolean createIfNeed) throws Exception {
			TableRuntimeJoin joinTableRuntime = tableCriterion.getJoin(sourceTableMetadata);
			if(null == joinTableRuntime && createIfNeed){
				joinTableRuntime = tableCriterion.newJoin(tableDefinition, sourceTableMetadata);
				joinTableRuntime.on(type, masterField, joinField);
			}
			return joinTableRuntime;
		}
	}
	
	//--------------------fields
	public DefaultFieldDefinition field(String key, TableDefinitionResource tableDefinition, FieldMetadata sourceFieldMetadata){
		return _field(new DefaultFieldDefinition(key, this, tableDefinition, sourceFieldMetadata));
	}
	
	public ConcatFieldDefintion concat(String key){
		return _field(new ConcatFieldDefintion(key, this));
	}
	
	public FormatFieldDefinition format(String key, String format){
		return _field(new FormatFieldDefinition(key, this, format));
	}
	
	public FieldDefinition field(String key, FieldDefinition fieldDefinition){
		return _field(new ProxyFieldDefinition(key, fieldDefinition));
	}
	
	protected <T extends FieldDefinition> T _field(T fieldDefinition){
		
		/*
		 * 注意：这里如果使用FieldDefinition.fieldName的话，有可能出现重新的字段名；
		 * 如果使用FieldDefinition[tableName + fieldName]，情况比较复杂，也可考虑
		 */
		put(fieldDefinition.getKey(), fieldDefinition);
		return fieldDefinition;
	}
	
	public final class ConcatFieldDefintion extends AbstractMultipleFieldDefinition{

		protected ConcatFieldDefintion(String key, TableDefinition searchableContext) {
			super(key, searchableContext);
		}

		@Override
		public SelectFragment buildFragment(TableCriterion tableCriterion) throws Exception {
			ArraySelectFragment arrayFragment = tableCriterion.array(title);
			_loadFields(tableCriterion, arrayFragment);
			return arrayFragment;
		}

		@Override
		public DatabaseType getDataType() {
			return DatabaseType.STRING;
		}



	}
	
	public final class FormatFieldDefinition extends AbstractMultipleFieldDefinition{

		protected FormatFieldDefinition(String key, TableDefinition searchableContext, String format) {
			super(key, searchableContext);
			
			this.format = format;
		}

		@Override
		public SelectFragment buildFragment(TableCriterion tableCriterion) throws Exception {
			FormatSelectFragment formatFragment = tableCriterion.format(title, format.toString());
			_loadFields(tableCriterion, formatFragment);
			return formatFragment;
		}

		@Override
		public DatabaseType getDataType() {
			return DatabaseType.STRING;
		}

		
	}
	
	public final class DefaultFieldDefinition extends AbstractSingleFieldDefinition{
		
		/** 这个表有可能是主表，也有可以是关联表 */
		private final TableDefinitionResource sourceTableDefinition;
		private final FieldMetadata sourceFieldMetadata;
		
		protected DefaultFieldDefinition(String key, TableDefinition tableDefinition, 
				TableDefinitionResource sourceTableDefinition, //字段所属的表，有可能是主表，也有可能是关联表 
				FieldMetadata sourceFieldMetadata) {
			super(key, tableDefinition);
			
			this.format = sourceFieldMetadata.getFormat();
			this.title = sourceFieldMetadata.getTitle();
			
			this.sourceTableDefinition = sourceTableDefinition;
			this.sourceFieldMetadata   = sourceFieldMetadata;
		}

		@Override
		public DatabaseType getDataType() {
			return sourceFieldMetadata.getDataType();
		}

		@Override
		public boolean contains(FieldMetadata fieldMetadata) {
			return fieldMetadata == sourceFieldMetadata;
		}
		
		@Override
		public FieldMetadata getSourceField() {
			return sourceFieldMetadata;
		}
		
		@Override
		public TableDefinitionResource getSourceTable() {
			return sourceTableDefinition;
		}

		@Override
		public SelectFragment buildFragment(TableCriterion tableCriterion) throws Exception {
			TableInstance tableSource = tableCriterion.safeLoadSource(tableCriterion, sourceTableDefinition, true);
			
			DefaultSelectFragment selectFragment = tableCriterion.field(tableSource, sourceFieldMetadata);
			selectFragment.setFormat(getFormat());
			selectFragment.setTitle(getTitle());
			selectFragment.setGroup(getGroup());
			return selectFragment;
		}

		
	}

	public boolean contains(FieldMetadata fieldMetadata) {
		for(FieldDefinition fieldDefinition : columnMapped.values()){
			if(fieldDefinition.contains(fieldMetadata)){
				return true;
			}
		}
		return false;
	}
	
	@Override
	public FieldMetadata get(String fieldName) {
		FieldMetadata fm = columnMapped.get(fieldName);
		if(null == fm) fm = sourceTableMetadata.get(fieldName);
		return fm;
	}

	@Override
	public Collection<? extends FieldMetadata> getFieldMetadatas() {
		List<FieldMetadata> items = new ArrayList<>(sourceTableMetadata.getFieldMetadatas());
		items.addAll(Arrays.asList(columns));
		return items;
	}

	@Override
	public FieldDefinition getDefinition(String fieldName) {
		return columnMapped.get(fieldName);
	}
	
	@Override
	public FieldDefinition getDefinition(int fieldIndex) {
		return columns[fieldIndex];
	}
	
	@Override
	public FieldDefinition[] getColumns() {
		return columns;
	}

	@Override
	public List<JoinDefinition> getMustJoinTables() {
		return mustJoinTables;
	}
}
